/*
Copyright 2024 New Vector Ltd.
Copyright 2022 The Matrix.org Foundation C.I.C.

SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Commercial
Please see LICENSE files in the repository root for full details.
*/

import React from "react";
import { type MatrixClient, type Room, EventType } from "matrix-js-sdk/src/matrix";
import { mocked } from "jest-mock";
import { act, render, screen, fireEvent, type RenderResult } from "jest-matrix-react";

import SpaceStore from "../../../../../src/stores/spaces/SpaceStore";
import { MetaSpace } from "../../../../../src/stores/spaces";
import _RoomListHeader from "../../../../../src/components/views/rooms/LegacyRoomListHeader";
import * as testUtils from "../../../../test-utils";
import { stubClient, mkSpace } from "../../../../test-utils";
import DMRoomMap from "../../../../../src/utils/DMRoomMap";
import { MatrixClientPeg } from "../../../../../src/MatrixClientPeg";
import SettingsStore from "../../../../../src/settings/SettingsStore";
import { SettingLevel } from "../../../../../src/settings/SettingLevel";
import { shouldShowComponent } from "../../../../../src/customisations/helpers/UIComponents";
import { UIComponent } from "../../../../../src/settings/UIFeature";

const RoomListHeader = testUtils.wrapInMatrixClientContext(_RoomListHeader);

jest.mock("../../../../../src/customisations/helpers/UIComponents", () => ({
    shouldShowComponent: jest.fn(),
}));

const blockUIComponent = (component: UIComponent): void => {
    mocked(shouldShowComponent).mockImplementation((feature) => feature !== component);
};

const setupSpace = (client: MatrixClient): Room => {
    const testSpace: Room = mkSpace(client, "!space:server");
    testSpace.name = "Test Space";
    client.getRoom = () => testSpace;
    return testSpace;
};

const setupMainMenu = async (client: MatrixClient, testSpace: Room): Promise<RenderResult> => {
    await testUtils.setupAsyncStoreWithClient(SpaceStore.instance, client);
    act(() => {
        SpaceStore.instance.setActiveSpace(testSpace.roomId);
    });

    const wrapper = render(<RoomListHeader />);

    expect(wrapper.container.textContent).toBe("Test Space");
    act(() => {
        wrapper.container.querySelector<HTMLElement>('[aria-label="Test Space menu"]')?.click();
    });

    return wrapper;
};

const setupPlusMenu = async (client: MatrixClient, testSpace: Room): Promise<RenderResult> => {
    await testUtils.setupAsyncStoreWithClient(SpaceStore.instance, client);
    act(() => {
        SpaceStore.instance.setActiveSpace(testSpace.roomId);
    });

    const wrapper = render(<RoomListHeader />);

    expect(wrapper.container.textContent).toBe("Test Space");
    act(() => {
        wrapper.container.querySelector<HTMLElement>('[aria-label="Add"]')?.click();
    });

    return wrapper;
};

const checkIsDisabled = (menuItem: HTMLElement): void => {
    expect(menuItem).toHaveAttribute("disabled");
    expect(menuItem).toHaveAttribute("aria-disabled", "true");
};

const checkMenuLabels = (items: NodeListOf<Element>, labelArray: Array<string>) => {
    expect(items).toHaveLength(labelArray.length);

    const checkLabel = (item: Element, label: string) => {
        expect(item.querySelector(".mx_IconizedContextMenu_label")?.textContent).toBe(label);
    };

    labelArray.forEach((label, index) => {
        console.log("index", index, "label", label);
        checkLabel(items[index], label);
    });
};

describe("RoomListHeader", () => {
    let client: MatrixClient;

    beforeEach(async () => {
        // This tests the header from the old room list/left panel, the new one is now enabled by default,
        // so needs to be explicitly disabled to test the old list here.
        await SettingsStore.setValue("feature_new_room_list", null, SettingLevel.DEVICE, false);
        jest.resetAllMocks();

        const dmRoomMap = {
            getUserIdForRoomId: jest.fn(),
            getDMRoomsForUserId: jest.fn(),
        } as unknown as DMRoomMap;
        DMRoomMap.setShared(dmRoomMap);
        stubClient();
        client = MatrixClientPeg.safeGet();
        mocked(shouldShowComponent).mockReturnValue(true); // show all UIComponents
    });

    it("renders a main menu for the home space", () => {
        act(() => {
            SpaceStore.instance.setActiveSpace(MetaSpace.Home);
        });

        const { container } = render(<RoomListHeader />);

        expect(container.textContent).toBe("Home");
        fireEvent.click(screen.getByLabelText("Home options"));

        const menu = screen.getByRole("menu");
        const items = menu.querySelectorAll(".mx_IconizedContextMenu_item");
        expect(items).toHaveLength(1);
        expect(items[0].textContent).toBe("Show all rooms");
    });

    it("renders a main menu for spaces", async () => {
        const testSpace = setupSpace(client);
        await setupMainMenu(client, testSpace);

        const menu = screen.getByRole("menu");
        const items = menu.querySelectorAll(".mx_IconizedContextMenu_item");

        checkMenuLabels(items, ["Space home", "Manage & explore rooms", "Preferences", "Settings", "Room", "Space"]);
    });

    it("renders a plus menu for spaces", async () => {
        const testSpace = setupSpace(client);
        await setupPlusMenu(client, testSpace);

        const menu = screen.getByRole("menu");
        const items = menu.querySelectorAll(".mx_IconizedContextMenu_item");

        checkMenuLabels(items, ["New room", "Explore rooms", "Add existing room", "Add space"]);
    });

    it("closes menu if space changes from under it", async () => {
        await SettingsStore.setValue("Spaces.enabledMetaSpaces", null, SettingLevel.DEVICE, {
            [MetaSpace.Home]: true,
            [MetaSpace.Favourites]: true,
        });

        const testSpace = setupSpace(client);
        await setupMainMenu(client, testSpace);

        act(() => {
            SpaceStore.instance.setActiveSpace(MetaSpace.Favourites);
        });

        screen.getByText("Favourites");
        expect(screen.queryByRole("menu")).toBeFalsy();
    });

    describe("UIComponents", () => {
        describe("Main menu", () => {
            it("does not render Add Space when user does not have permission to add spaces", async () => {
                // User does not have permission to add spaces, anywhere
                blockUIComponent(UIComponent.CreateSpaces);

                const testSpace = setupSpace(client);
                await setupMainMenu(client, testSpace);

                const menu = screen.getByRole("menu");
                const items = menu.querySelectorAll(".mx_IconizedContextMenu_item");
                checkMenuLabels(items, [
                    "Space home",
                    "Manage & explore rooms",
                    "Preferences",
                    "Settings",
                    "Room",
                    // no add space
                ]);
            });

            it("does not render Add Room when user does not have permission to add rooms", async () => {
                // User does not have permission to add rooms
                blockUIComponent(UIComponent.CreateRooms);

                const testSpace = setupSpace(client);
                await setupMainMenu(client, testSpace);

                const menu = screen.getByRole("menu");
                const items = menu.querySelectorAll(".mx_IconizedContextMenu_item");
                checkMenuLabels(items, [
                    "Space home",
                    "Explore rooms", // not Manage & explore rooms
                    "Preferences",
                    "Settings",
                    // no add room
                    "Space",
                ]);
            });
        });

        describe("Plus menu", () => {
            it("does not render Add Space when user does not have permission to add spaces", async () => {
                // User does not have permission to add spaces, anywhere
                blockUIComponent(UIComponent.CreateSpaces);

                const testSpace = setupSpace(client);
                await setupPlusMenu(client, testSpace);

                const menu = screen.getByRole("menu");
                const items = menu.querySelectorAll(".mx_IconizedContextMenu_item");

                checkMenuLabels(items, [
                    "New room",
                    "Explore rooms",
                    "Add existing room",
                    // no Add space
                ]);
            });

            it("disables Add Room when user does not have permission to add rooms", async () => {
                // User does not have permission to add rooms
                blockUIComponent(UIComponent.CreateRooms);

                const testSpace = setupSpace(client);
                await setupPlusMenu(client, testSpace);

                const menu = screen.getByRole("menu");
                const items = menu.querySelectorAll<HTMLElement>(".mx_IconizedContextMenu_item");

                checkMenuLabels(items, ["New room", "Explore rooms", "Add existing room", "Add space"]);

                // "Add existing room" is disabled
                checkIsDisabled(items[2]);
            });
        });
    });

    describe("adding children to space", () => {
        it("if user cannot add children to space, MainMenu adding buttons are hidden", async () => {
            const testSpace = setupSpace(client);
            mocked(testSpace.currentState.maySendStateEvent).mockImplementation(
                (stateEventType, userId) => stateEventType !== EventType.SpaceChild,
            );

            await setupMainMenu(client, testSpace);

            const menu = screen.getByRole("menu");
            const items = menu.querySelectorAll(".mx_IconizedContextMenu_item");
            checkMenuLabels(items, [
                "Space home",
                "Explore rooms", // not Manage & explore rooms
                "Preferences",
                "Settings",
                // no add room
                // no add space
            ]);
        });

        it("if user cannot add children to space, PlusMenu add buttons are disabled", async () => {
            const testSpace = setupSpace(client);
            mocked(testSpace.currentState.maySendStateEvent).mockImplementation(
                (stateEventType, userId) => stateEventType !== EventType.SpaceChild,
            );

            await setupPlusMenu(client, testSpace);

            const menu = screen.getByRole("menu");
            const items = menu.querySelectorAll<HTMLElement>(".mx_IconizedContextMenu_item");

            checkMenuLabels(items, ["New room", "Explore rooms", "Add existing room", "Add space"]);

            // "Add existing room" is disabled
            checkIsDisabled(items[2]);
            // "Add space" is disabled
            checkIsDisabled(items[3]);
        });
    });
});
